﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Threading;
using System.Threading.Tasks;
using K9Nano.Domain;
using Microsoft.EntityFrameworkCore;
using Z.EntityFramework.Plus;

namespace K9Nano.EFCore
{
    public class EfRepositoryBase<TEntity, TKey, TDbContext> : RepositoryBase<TEntity, TKey>
        where TEntity : class, IEntity<TKey>
        where TKey : IEquatable<TKey>
        where TDbContext : class, IK9DbContext
    {
        protected readonly TDbContext DbContext;

        protected readonly DbSet<TEntity> Table;

        public EfRepositoryBase(TDbContext context)
        {
            DbContext = context;
            Table = DbContext.Set<TEntity>();
        }

        public override IQueryable<TEntity> Query()
        {
            return DbContext.Queryable<TEntity>();
        }

        public override IQueryable<TEntity> QueryAsNoTracking()
        {
            return Query().AsNoTracking();
        }

        public override IQueryable<TEntity> GetAllIncluding(params Expression<Func<TEntity, object>>[] propertySelectors)
        {
            var query = Query();

            foreach (var selector in propertySelectors)
            {
                query = query.Include(selector);
            }

            return query;
        }

        public override async ValueTask<List<TEntity>> GetAllListAsync(CancellationToken cancellation = default)
        {
            return await Query().ToListAsync(cancellation);
        }

        public override async ValueTask<List<TEntity>> GetAllListAsync(Expression<Func<TEntity, bool>> predicate, CancellationToken cancellation = default)
        {
            var query = Query();
            if (predicate != null)
            {
                query = query.Where(predicate);
            }
            return await query.ToListAsync(cancellation);
        }

        public override async ValueTask<List<TEntity>> GetPagedListAsync(Expression<Func<TEntity, bool>> predicate,
            Func<IQueryable<TEntity>, IQueryable<TEntity>> orderByCreator, int skip, int take,
            CancellationToken cancellation = default)
        {
            var query = Query();
            if (predicate != null)
            {
                query = query.Where(predicate);
            }
            query = orderByCreator(query);
            return await query.Skip(skip)
                .Take(take)
                .ToListAsync(cancellation);
        }

        public override async ValueTask<List<TEntity>> GetPagedListAsync(Expression<Func<TEntity, bool>> predicate,
            Expression<Func<TEntity, object>> orderBy,
            int skip,
            int take,
            CancellationToken cancellation = default)
        {
            var query = Query();
            if (predicate != null)
            {
                query = query.Where(predicate);
            }
            return await query
                .OrderBy(orderBy)
                .Skip(skip)
                .Take(take)
                .ToListAsync(cancellation);
        }

        public override async ValueTask<List<TEntity>> GetPagedListAsync(Expression<Func<TEntity, object>> orderBy,
            int skip,
            int take,
            CancellationToken cancellation = default)
        {
            return await Query()
                .OrderBy(orderBy)
                .Skip(skip)
                .Take(take)
                .ToListAsync(cancellation);
        }

        public override async ValueTask<TEntity> SingleAsync(Expression<Func<TEntity, bool>> predicate, CancellationToken cancellation = default)
        {
            return await Query().SingleAsync(predicate, cancellation);
        }

        public override async ValueTask<TEntity> FirstOrDefaultAsync(Expression<Func<TEntity, bool>> predicate, CancellationToken cancellation = default)
        {
            return await Query().FirstOrDefaultAsync(predicate, cancellation);
        }

        public override async ValueTask<TEntity> FirstOrDefaultAsync(Expression<Func<TEntity, bool>> predicate, Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>> configOrderBy, CancellationToken cancellation = default)
        {
            return await configOrderBy(Query()).FirstOrDefaultAsync(predicate, cancellation);
        }

        public override async ValueTask<bool> AnyAsync(CancellationToken cancellation)
        {
            return await Query().AnyAsync(cancellation);
        }

        public override async ValueTask<bool> AnyAsync(Expression<Func<TEntity, bool>> predicate, CancellationToken cancellation)
        {
            return await Query().AnyAsync(predicate, cancellation);
        }

        public override TEntity Insert(TEntity entity)
        {
            var result = Table.Add(entity).Entity;
            DbContext.SaveChanges();
            return result;
        }

        public override async ValueTask<TEntity> InsertAsync(TEntity entity, CancellationToken cancellation = default)
        {
            var result = (await Table.AddAsync(entity, cancellation)).Entity;
            await DbContext.SaveChangesAsync(true, cancellation);
            return result;
        }

        public override int AddRange(IEnumerable<TEntity> entities)
        {
            var enabled = DbContext.ChangeTracker.AutoDetectChangesEnabled;
            DbContext.ChangeTracker.AutoDetectChangesEnabled = false;
            try
            {
                Table.AddRange(entities);
                return DbContext.SaveChanges(true);
            }
            finally
            {
                DbContext.ChangeTracker.AutoDetectChangesEnabled = enabled;
            }
        }

        public override async ValueTask<int> AddRangeAsync(IEnumerable<TEntity> entities, CancellationToken cancellation = default)
        {
            var enabled = DbContext.ChangeTracker.AutoDetectChangesEnabled;
            DbContext.ChangeTracker.AutoDetectChangesEnabled = false;
            try
            {
                await Table.AddRangeAsync(entities, cancellation);
                return await DbContext.SaveChangesAsync(true, cancellation);
            }
            finally
            {
                DbContext.ChangeTracker.AutoDetectChangesEnabled = enabled;
            }
        }

        public override TEntity Update(TEntity entity)
        {
            AttachIfNot(entity);
            DbContext.Entry(entity).State = EntityState.Modified;
            DbContext.SaveChanges();
            return entity;
        }

        public override async ValueTask<TEntity> UpdateAsync(TEntity entity, CancellationToken cancellation = default)
        {
            AttachIfNot(entity);
            DbContext.Entry(entity).State = EntityState.Modified;
            await DbContext.SaveChangesAsync(true, cancellation);
            return entity;
        }

        public override async ValueTask UpdateAsync(Expression<Func<TEntity, bool>> predicate, TEntity updated, CancellationToken cancellation = default)
        {
            PrepareForUpdate(updated);
            await Table.Where(predicate).UpdateAsync(x => updated, cancellation);
        }

        public override void Delete(TEntity entity, bool soft = true)
        {
            if (soft && entity is ISoftDelete dt)
            {
                dt.IsDeleted = true;
                Update(entity);
                return;
            }
            AttachIfNot(entity);
            Table.Remove(entity);
            DbContext.SaveChanges();
        }

        public override async ValueTask DeleteAsync(TEntity entity, bool soft = true, CancellationToken cancellation = default)
        {
            if (soft && entity is ISoftDelete dt)
            {
                dt.IsDeleted = true;
                await UpdateAsync(entity, cancellation);
                return;
            }
            AttachIfNot(entity);
            Table.Remove(entity);
            await DbContext.SaveChangesAsync(true, cancellation);
        }

        public override TEntity Delete(TKey id, bool soft = true)
        {
            var entity = GetFromChangeTrackerOrNull(id) ?? Table.Find(id);

            if (entity != null)
            {
                Delete(entity, soft);
            }

            return entity;
        }

        public override async ValueTask<TEntity> DeleteAsync(TKey id, bool soft = true, CancellationToken cancellation = default)
        {
            var entity = GetFromChangeTrackerOrNull(id) ?? (await Table.FindAsync(id));

            if (entity != null)
            {
                await DeleteAsync(entity, soft, cancellation);
            }

            return entity;
        }

        public override async ValueTask DeleteAsync(Expression<Func<TEntity, bool>> predicate, bool soft = true, int batchSize = 200, CancellationToken cancellation = default)
        {
            if (soft && typeof(TEntity).IsAssignableFrom(typeof(ISoftDelete)))
            {
                var entity = Activator.CreateInstance<TEntity>();
                ((ISoftDelete)entity).IsDeleted = true;
                await UpdateAsync(predicate, entity, cancellation);
                return;
            }

            await Table.Where(predicate).DeleteAsync(x => x.BatchSize = batchSize, cancellationToken: cancellation);
        }

        public override async ValueTask<int> CountAsync(CancellationToken cancellation = default)
        {
            return await Query().CountAsync(cancellation);
        }

        public override async ValueTask<int> CountAsync(Expression<Func<TEntity, bool>> predicate, CancellationToken cancellation = default)
        {
            return await Query().CountAsync(predicate, cancellation);
        }

        public override async ValueTask<long> LongCountAsync(CancellationToken cancellation = default)
        {
            return await Query().LongCountAsync(cancellation);
        }

        public override async ValueTask<long> LongCountAsync(Expression<Func<TEntity, bool>> predicate, CancellationToken cancellation = default)
        {
            return await Query().LongCountAsync(predicate, cancellation);
        }

        public override int Sum(Expression<Func<TEntity, bool>> predicate, Expression<Func<TEntity, int>> selector)
        {
            return Query().Where(predicate).Sum(selector);
        }

        public override long Sum(Expression<Func<TEntity, bool>> predicate, Expression<Func<TEntity, long>> selector)
        {
            return Query().Where(predicate).Sum(selector);
        }

        public override float Sum(Expression<Func<TEntity, bool>> predicate, Expression<Func<TEntity, float>> selector)
        {
            return Query().Where(predicate).Sum(selector);
        }

        public override double Sum(Expression<Func<TEntity, bool>> predicate, Expression<Func<TEntity, double>> selector)
        {
            return Query().Where(predicate).Sum(selector);
        }

        public override decimal Sum(Expression<Func<TEntity, bool>> predicate, Expression<Func<TEntity, decimal>> selector)
        {
            return Query().Where(predicate).Sum(selector);
        }

        public override async ValueTask<int> SumAsync(Expression<Func<TEntity, bool>> predicate, Expression<Func<TEntity, int>> selector, CancellationToken cancellation)
        {
            return await Query().Where(predicate).SumAsync(selector, cancellation);
        }

        public override async ValueTask<long> SumAsync(Expression<Func<TEntity, bool>> predicate, Expression<Func<TEntity, long>> selector, CancellationToken cancellation)
        {
            return await Query().Where(predicate).SumAsync(selector, cancellation);
        }

        public override async ValueTask<float> SumAsync(Expression<Func<TEntity, bool>> predicate, Expression<Func<TEntity, float>> selector, CancellationToken cancellation)
        {
            return await Query().Where(predicate).SumAsync(selector, cancellation);
        }

        public override async ValueTask<double> SumAsync(Expression<Func<TEntity, bool>> predicate, Expression<Func<TEntity, double>> selector, CancellationToken cancellation)
        {
            return await Query().Where(predicate).SumAsync(selector, cancellation);
        }

        public override async ValueTask<decimal> SumAsync(Expression<Func<TEntity, bool>> predicate, Expression<Func<TEntity, decimal>> selector, CancellationToken cancellation)
        {
            return await Query().Where(predicate).SumAsync(selector, cancellation);
        }

        public override int Min(Expression<Func<TEntity, bool>> predicate, Expression<Func<TEntity, int>> selector)
        {
            return Query().Where(predicate).Min(selector);
        }

        public override long Min(Expression<Func<TEntity, bool>> predicate, Expression<Func<TEntity, long>> selector)
        {
            return Query().Where(predicate).Min(selector);
        }

        public override float Min(Expression<Func<TEntity, bool>> predicate, Expression<Func<TEntity, float>> selector)
        {
            return Query().Where(predicate).Min(selector);
        }

        public override double Min(Expression<Func<TEntity, bool>> predicate, Expression<Func<TEntity, double>> selector)
        {
            return Query().Where(predicate).Min(selector);
        }

        public override decimal Min(Expression<Func<TEntity, bool>> predicate, Expression<Func<TEntity, decimal>> selector)
        {
            return Query().Where(predicate).Min(selector);
        }

        public override async ValueTask<int> MinAsync(Expression<Func<TEntity, bool>> predicate, Expression<Func<TEntity, int>> selector, CancellationToken cancellation)
        {
            return await Query().Where(predicate).MinAsync(selector, cancellation);
        }

        public override async ValueTask<long> MinAsync(Expression<Func<TEntity, bool>> predicate, Expression<Func<TEntity, long>> selector, CancellationToken cancellation)
        {
            return await Query().Where(predicate).MinAsync(selector, cancellation);
        }

        public override async ValueTask<float> MinAsync(Expression<Func<TEntity, bool>> predicate, Expression<Func<TEntity, float>> selector, CancellationToken cancellation)
        {
            return await Query().Where(predicate).MinAsync(selector, cancellation);
        }

        public override async ValueTask<double> MinAsync(Expression<Func<TEntity, bool>> predicate, Expression<Func<TEntity, double>> selector, CancellationToken cancellation)
        {
            return await Query().Where(predicate).MinAsync(selector, cancellation);
        }

        public override async ValueTask<decimal> MinAsync(Expression<Func<TEntity, bool>> predicate, Expression<Func<TEntity, decimal>> selector, CancellationToken cancellation)
        {
            return await Query().Where(predicate).MinAsync(selector, cancellation);
        }

        public override int Max(Expression<Func<TEntity, bool>> predicate, Expression<Func<TEntity, int>> selector)
        {
            return Query().Where(predicate).Max(selector);
        }

        public override long Max(Expression<Func<TEntity, bool>> predicate, Expression<Func<TEntity, long>> selector)
        {
            return Query().Where(predicate).Max(selector);
        }

        public override float Max(Expression<Func<TEntity, bool>> predicate, Expression<Func<TEntity, float>> selector)
        {
            return Query().Where(predicate).Max(selector);
        }

        public override double Max(Expression<Func<TEntity, bool>> predicate, Expression<Func<TEntity, double>> selector)
        {
            return Query().Where(predicate).Max(selector);
        }

        public override decimal Max(Expression<Func<TEntity, bool>> predicate, Expression<Func<TEntity, decimal>> selector)
        {
            return Query().Where(predicate).Max(selector);
        }

        public override async ValueTask<int> MaxAsync(Expression<Func<TEntity, bool>> predicate, Expression<Func<TEntity, int>> selector, CancellationToken cancellation)
        {
            return await Query().Where(predicate).MaxAsync(selector, cancellation);
        }

        public override async ValueTask<long> MaxAsync(Expression<Func<TEntity, bool>> predicate, Expression<Func<TEntity, long>> selector, CancellationToken cancellation)
        {
            return await Query().Where(predicate).MaxAsync(selector, cancellation);
        }

        public override async ValueTask<float> MaxAsync(Expression<Func<TEntity, bool>> predicate, Expression<Func<TEntity, float>> selector, CancellationToken cancellation)
        {
            return await Query().Where(predicate).MaxAsync(selector, cancellation);
        }

        public override async ValueTask<double> MaxAsync(Expression<Func<TEntity, bool>> predicate, Expression<Func<TEntity, double>> selector, CancellationToken cancellation)
        {
            return await Query().Where(predicate).MaxAsync(selector, cancellation);
        }

        public override async ValueTask<decimal> MaxAsync(Expression<Func<TEntity, bool>> predicate, Expression<Func<TEntity, decimal>> selector, CancellationToken cancellation)
        {
            return await Query().Where(predicate).MaxAsync(selector, cancellation);
        }

        public override double Average(Expression<Func<TEntity, bool>> predicate, Expression<Func<TEntity, int>> selector)
        {
            return Query().Where(predicate).Average(selector);
        }

        public override double Average(Expression<Func<TEntity, bool>> predicate, Expression<Func<TEntity, long>> selector)
        {
            return Query().Where(predicate).Average(selector);
        }

        public override float Average(Expression<Func<TEntity, bool>> predicate, Expression<Func<TEntity, float>> selector)
        {
            return Query().Where(predicate).Average(selector);
        }

        public override double Average(Expression<Func<TEntity, bool>> predicate, Expression<Func<TEntity, double>> selector)
        {
            return Query().Where(predicate).Average(selector);
        }

        public override decimal Average(Expression<Func<TEntity, bool>> predicate, Expression<Func<TEntity, decimal>> selector)
        {
            return Query().Where(predicate).Average(selector);
        }

        public override async ValueTask<double> AverageAsync(Expression<Func<TEntity, bool>> predicate, Expression<Func<TEntity, int>> selector, CancellationToken cancellation)
        {
            return await Query().Where(predicate).AverageAsync(selector, cancellation);
        }

        public override async ValueTask<double> AverageAsync(Expression<Func<TEntity, bool>> predicate, Expression<Func<TEntity, long>> selector, CancellationToken cancellation)
        {
            return await Query().Where(predicate).AverageAsync(selector, cancellation);
        }

        public override async ValueTask<float> AverageAsync(Expression<Func<TEntity, bool>> predicate, Expression<Func<TEntity, float>> selector, CancellationToken cancellation)
        {
            return await Query().Where(predicate).AverageAsync(selector, cancellation);
        }

        public override async ValueTask<double> AverageAsync(Expression<Func<TEntity, bool>> predicate, Expression<Func<TEntity, double>> selector, CancellationToken cancellation)
        {
            return await Query().Where(predicate).AverageAsync(selector, cancellation);
        }

        public override async ValueTask<decimal> AverageAsync(Expression<Func<TEntity, bool>> predicate, Expression<Func<TEntity, decimal>> selector, CancellationToken cancellation)
        {
            return await Query().Where(predicate).AverageAsync(selector, cancellation);
        }

        protected virtual void AttachIfNot(TEntity entity)
        {
            var entry = DbContext.ChangeTracker.Entries().FirstOrDefault(ent => ent.Entity == entity);
            if (entry != null)
            {
                return;
            }

            Table.Attach(entity);
        }

        private TEntity GetFromChangeTrackerOrNull(TKey id)
        {
            var entry = DbContext.ChangeTracker.Entries()
                .FirstOrDefault(x => x.Entity is TEntity ent && ent.Id.Equals(id));

            return entry?.Entity as TEntity;
        }

        protected virtual void PrepareForUpdate(TEntity entity)
        {
            if (entity is IModificationAudited at)
            {
                at.LastModificationTime = DateTime.Now;
                at.LastModifierUserId = DbContext.SessionContext.UserId;
            }
        }
    }
}